﻿using System;
using System.Xml;
using Notoric.Model.Collections;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Validation;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// A common base class for attachments that connect two durational symbols.
    /// </summary>
    public abstract class ConnectorElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="ConnectorElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CompositionElement"/>
        /// </para>
        /// <para>
        /// The composition that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="start">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol at which this connector starts.
        /// </para>
        /// </param>
        /// <param name="end">
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol at which this connector ends.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// The <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        /// <exception cref="ValidationException">
        /// The <paramref name="start"/> or <paramref name="end"/> is not valid.
        /// </exception>
        protected ConnectorElement(CompositionPart packagePart, CompositionElement owner, DurationalSymbolElement start, DurationalSymbolElement end)
            : base(packagePart, owner)
        {
            Start = start;
            End = end;
            typeAttribute.Value = GetType().FullName;
        }

        /// <summary>
        /// Creates a new <see cref="ConnectorElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="CompositionElement"/>
        /// </para>
        /// <para>
        /// The composition that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        protected ConnectorElement(CompositionPart packagePart, CompositionElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            startAttribute = element.Attributes[PackageVocabulary.StartAttribute];
            endAttribute = element.Attributes[PackageVocabulary.EndAttribute];
            typeAttribute = element.Attributes[PackageVocabulary.TypeAttribute];
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = CreateSymbolElement(document);

            startAttribute = document.CreateAttribute(PackageVocabulary.StartAttribute);
            endAttribute = document.CreateAttribute(PackageVocabulary.EndAttribute);
            typeAttribute = document.CreateAttribute(PackageVocabulary.TypeAttribute);

            element.Attributes.Append(startAttribute);
            element.Attributes.Append(endAttribute);
            element.Attributes.Append(typeAttribute);

            return element;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, this method creates the XML element
        /// that will hold serialized data of this object.
        /// </summary>
        /// <remarks>
        /// The attributes for the inherited data fields as well as the type information
        /// will be added by the base class.
        /// </remarks>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The XML document that will containt the created element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created XML element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateSymbolElement(XmlDocument document);

        #endregion

        #region Pulic Properties

        /// <summary>
        /// Gets or sets the durational symbol at which this connector ends.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol at which this connector ends.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is <see langword="null"/>.
        /// </exception>
        /// TODO: Write the validation rules
        public DurationalSymbolElement End
        {
            get
            {
                if (end == null)
                    end = PackagePart.Package.IDMap.FindObject<DurationalSymbolElement>(new Guid(endAttribute.Value));

                return end;
            }
            set
            {
                ValidateAndThrow("End", value);

                end = value;
                endAttribute.Value = value.ID.ToString();

                RaisePropertyChanged("End");
            }
        }

        /// <summary>
        /// Gets or sets the durational symbol at which this connector starts.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="DurationalSymbolElement"/>
        /// </para>
        /// <para>
        /// The durational symbol at which this connector starts.
        /// </para>
        /// </value>
        /// <exception cref="ValidationException">
        /// The <paramref name="value"/> is <see langword="null"/>.
        /// </exception>
        /// TODO: Write the validation rules
        public DurationalSymbolElement Start
        {
            get
            {
                if (start == null)
                    start = PackagePart.Package.IDMap.FindObject<DurationalSymbolElement>(new Guid(startAttribute.Value));

                return start;
            }
            set
            {
                ValidateAndThrow("Start", value);

                start = value;
                startAttribute.Value = value.ID.ToString();

                RaisePropertyChanged("Start");
            }
        }
        
        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the value of the <see cref="End"/> property
        /// </summary>
        private DurationalSymbolElement end;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="End"/> property.
        /// </summary>
        private XmlAttribute endAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Start"/> property.
        /// </summary>
        private DurationalSymbolElement start;
        /// <summary>
        /// Holds the XML attribute that holds the value of the <see cref="Start"/> property.
        /// </summary>
        private XmlAttribute startAttribute;
        /// <summary>
        /// Holds the XML attribute that holds the type of the connector class.
        /// </summary>
        private XmlAttribute typeAttribute;

        #endregion
    }
}
